package com.weibei.web.config;

import com.alibaba.fastjson.JSON;
import com.weibei.db.domain.web.TbInterfaceMonitor;
import com.weibei.interfaces.service.system.SysInterfaceMonitorService;
import javassist.*;
import javassist.bytecode.CodeAttribute;
import javassist.bytecode.LocalVariableAttribute;
import javassist.bytecode.MethodInfo;
import org.apache.commons.lang.ArrayUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Arrays;

@Aspect
@Component
public class RequestLogAspect {

    @Autowired
    private SysInterfaceMonitorService iSysInterfaceMonitorService;

    private static final Logger logger = LoggerFactory.getLogger(RequestLogAspect.class);

    @Pointcut("execution(public * com.weibei.web.controller.*.*(..))")
    public void optLog() {
    }

    @Around("optLog()")
    public Object doAround(ProceedingJoinPoint joinpoint) throws Throwable {
        String realIp = "";
        String reqUrl = "";
        StringBuffer sb = new StringBuffer();
        try {
            ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
            HttpServletRequest request = attributes.getRequest();
            realIp = getIpAddr(request);
            reqUrl = request.getRequestURL().toString();

            if(!StringUtils.isEmpty(reqUrl) && reqUrl.contains("/mp/footprint/add")){
                return joinpoint.proceed();
            }

            // 记录下请求内容
            sb.append(" URL : [").append(request.getMethod()).append("]").append(reqUrl)
                    .append(". \n 请求IP : ").append(realIp).append(". \n 方法 : ")
                    .append(joinpoint.getTarget().getClass().getName()).append(".")
                    .append(joinpoint.getSignature().getName()).append(". \n 参数列表 : ")
                    .append(Arrays.toString(joinpoint.getArgs()));
            logger.info(sb.toString());
        } catch (Exception e) {
            logger.error("输出请求参数错误", e.getCause());
        }
        String msgInfo = "@aop[" + joinpoint.getSignature().getDeclaringTypeName() + "." + joinpoint.getSignature().getName() + "]"; // 所在的类.方法
        String requestStr = getRequestParam(joinpoint);
        requestStr = parameterHandle(requestStr, 10000);
        logger.info(msgInfo + "start.输入参数：" + requestStr);
        long startTime = System.currentTimeMillis();// 开始时间
        Object result = null;
        try {
            // 执行完方法的返回值：调用proceed()方法，就会触发切入点方法执行
            result = joinpoint.proceed();// result的值就是被拦截方法的返回值
        } catch (Exception e) {
            throw e;
        } finally {
            long handleTime = System.currentTimeMillis() - startTime;// 开始时间
            String responseStr = result == null ? "无" : JSON.toJSONString(result);
            responseStr = parameterHandle(responseStr, 10000);

            StringBuffer endString = new StringBuffer(100);
            endString.append(msgInfo).append("end.");
            endString.append("耗时（" + handleTime + "ms）");
            endString.append("输出参数：").append(responseStr);
            logger.info(endString.toString());

//            try {
//                //异步插入接口监控表
//                String respCode = JSONObject.parseObject(JSONObject.parseArray(responseStr).get(0).toString()).getString("errno");
//                addInterfaceMonitor(realIp,reqUrl,requestStr,responseStr,respCode);
//            }catch (Exception e){
//                e.printStackTrace();
//                logger.error("addInterfaceMonitor error",e);
//            }
        }
        return result;
    }


    @AfterThrowing(pointcut = "optLog()", throwing = "e")
    public void afterThrowing(JoinPoint joinpoint, Throwable e) {
        logger.error("异常代码:" + e.getClass().getName());
        logger.error("\n 异常信息:" + e.getMessage());
        logger.error("\n 异常方法:" + joinpoint.getTarget().getClass().getName() + "." + joinpoint.getSignature().getName());
    }

    /**
     * @param paramStr
     * @param strlength
     * @return
     * @Description : 参数处理，超过指定长度字符的，只显示1000...
     * @author : 陈惟鲜 danger
     * @Date : 2018年8月10日 上午11:44:11
     */
    private String parameterHandle(String paramStr, int strlength) {
        if (paramStr.length() > strlength) {
            paramStr = paramStr.substring(0, 1000) + "...";
        }
        if (paramStr.length() > 10) {
            paramStr = "[" + paramStr + "]";
        }
        return paramStr;
    }

    /***
     * @Description : 获取请求参数
     * @author : 陈惟鲜 danger
     * @Date : 2018年8月9日 下午3:47:08
     * @param point
     * @return
     */
    private String getRequestParam(ProceedingJoinPoint point) {
        String class_name = point.getTarget().getClass().getName();
        String method_name = point.getSignature().getName();
        /**
         * 获取方法的参数值数组。
         */
        Object[] methodArgs = point.getArgs();

        String[] paramNames = null;
        // 结果
        String requestStr = "";
        /**
         * 获取方法参数名称
         */
        try {
            paramNames = getFieldsName(class_name, method_name);
            requestStr = logParam(paramNames, methodArgs);
        } catch (Exception e) {
            requestStr = "获取参数失败";
        }
        return requestStr;
    }

    /**
     * 使用javassist来获取方法参数名称
     *
     * @param class_name  类名
     * @param method_name 方法名
     * @return
     * @throws Exception
     */
    private String[] getFieldsName(String class_name, String method_name) throws Exception {
        Class<?> clazz = Class.forName(class_name);
        String clazz_name = clazz.getName();
        ClassPool pool = ClassPool.getDefault();
        ClassClassPath classPath = new ClassClassPath(clazz);
        pool.insertClassPath(classPath);

        CtClass ctClass = pool.get(clazz_name);
        CtMethod ctMethod = ctClass.getDeclaredMethod(method_name);
        MethodInfo methodInfo = ctMethod.getMethodInfo();
        CodeAttribute codeAttribute = methodInfo.getCodeAttribute();
        LocalVariableAttribute attr = (LocalVariableAttribute) codeAttribute.getAttribute(LocalVariableAttribute.tag);
        if (attr == null) {
            return null;
        }
        String[] paramsArgsName = new String[ctMethod.getParameterTypes().length];
        int pos = Modifier.isStatic(ctMethod.getModifiers()) ? 0 : 1;
        for (int i = 0; i < paramsArgsName.length; i++) {
            paramsArgsName[i] = attr.variableName(i + pos);
        }
        return paramsArgsName;
    }

    /**
     * 判断是否为基本类型：包括String
     *
     * @param clazz clazz
     * @return true：是;     false：不是
     */
    private boolean isPrimite(Class<?> clazz) {
        if (clazz.isPrimitive() || clazz == String.class) {
            return true;
        } else {
            return false;
        }
    }


    /**
     * 打印方法参数值  基本类型直接打印，非基本类型需要重写toString方法
     *
     * @param paramsArgsName  方法参数名数组
     * @param paramsArgsValue 方法参数值数组
     */
    private String logParam(String[] paramsArgsName, Object[] paramsArgsValue) {
        if (ArrayUtils.isEmpty(paramsArgsName) || ArrayUtils.isEmpty(paramsArgsValue)) {
            return "";
        }
        StringBuffer buffer = new StringBuffer();
        for (int i = 0; i < paramsArgsValue.length; i++) {
            //参数名
            String name = paramsArgsName[i];
            //参数值
            Object value = paramsArgsValue[i];
            buffer.append(name + " = ");
            if (isPrimite(value.getClass())) {
                buffer.append(value + "  ,");
            } else {
                buffer.append(value.toString() + "  ,");
            }
        }
        return buffer.toString();
    }

    /**
     * 获取真实ip
     *
     * @param request
     * @return
     */
    public static String getIpAddr(HttpServletRequest request) {
        String ipAddress = request.getHeader("x-forwarded-for");
        if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
            ipAddress = request.getHeader("Proxy-Client-IP");
        }
        if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
            ipAddress = request.getHeader("WL-Proxy-Client-IP");
        }
        if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
            ipAddress = request.getRemoteAddr();
            String localIp = "127.0.0.1";
            String localIpv6 = "0:0:0:0:0:0:0:1";
            if (ipAddress.equals(localIp) || ipAddress.equals(localIpv6)) {
                // 根据网卡取本机配置的IP
                InetAddress inet = null;
                try {
                    inet = InetAddress.getLocalHost();
                    ipAddress = inet.getHostAddress();
                } catch (UnknownHostException e) {
                    e.printStackTrace();
                }
            }
        }
        // 对于通过多个代理的情况，第一个IP为客户端真实IP,多个IP按照','分割
        String ipSeparate = ",";
        int ipLength = 15;
        if (ipAddress != null && ipAddress.length() > ipLength) {
            if (ipAddress.indexOf(ipSeparate) > 0) {
                ipAddress = ipAddress.substring(0, ipAddress.indexOf(ipSeparate));
            }
        }
        return ipAddress;
    }

    /**
     * 异步添加接口监控记录
     * @param ipAddr
     * @param method
     * @param reqParm
     * @param respMsg
     * @param respCode
     */
    @Async
    public void addInterfaceMonitor(String ipAddr,String method,String reqParm,String respMsg,String respCode){
        TbInterfaceMonitor interfaceMonitor = new TbInterfaceMonitor();
        interfaceMonitor.setIpAddr(ipAddr);
        interfaceMonitor.setReqUrl(method);
        interfaceMonitor.setReqParm(reqParm);
        interfaceMonitor.setRespMsg(respMsg);
        interfaceMonitor.setRespCode(respCode);
        iSysInterfaceMonitorService.save(interfaceMonitor);
    }
}
